/*
 * msgvolume.c
 * 
 * Copyright 2012 Kamil Cukrowski <kamil@dyzio.pl>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation version 2.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */
#define MSGVOLUME_VERSION "1.2.4"
#include <stdio.h>
#include <alsa/asoundlib.h>
#include <alsa/mixer.h>
#include <arpa/inet.h>
#include <signal.h>
#include <msgserv.h>

#define BAR_WIDTH 175
#define MSGPRI MSG_NOTICE+3
#define MY_NAME "msgvolume"
#define PINFO(smt, ...) do { printf(smt, ## __VA_ARGS__ ); printf("\n"); } while(0)
#define PDEBUG(str, ...) PDEBUGL(1, str, ##__VA_ARGS__);
#define PDEBUGL(x, str, ...) do{ if ( debug >= x ) _INTERNAL_LOGIT(str, ##__VA_ARGS__); }while(0)
#define PERROR(str, ...) do { printf(MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("error %d:%s \n", errno, strerror(errno)); }while(0)
#define _INTERNAL_LOGIT(str, ...) do { printf( MY_NAME ": (%s:%d): ", __FILE__, __LINE__); printf(str, ##__VA_ARGS__); printf("\n"); }while(0)

int debug = 0;
int refresh_time = 20;
int sockfd;
in_addr_t server_ip;
snd_mixer_t *mixer_handle;
int mixer_changed_state = 0;

void safe_exit()
{
	msglcd_disconnect(sockfd);
	snd_mixer_close(mixer_handle);
	exit(0);
}

#define fatal(format, ...) \
do {\
	PERROR(format, ##__VA_ARGS__);\
	safe_exit();\
} while (0)

void sig_hup (int param) 
{
	PINFO("Signal received. Goind down");
	safe_exit();
}

void parse_cmd(int argc, char **argv)
{
	int c=0;
	opterr=0;
	while ( (c = getopt(argc, argv, "hdr:i:")) != -1 )
		switch (c) {
		case 'h':
			printf("MsgLCD: messaging system to LCD geteway.\n"
				"\t-h\tdisplay this help.\n"
				"\t-d\tdebug mode on (do not fork into the background);\n"
				"\t-r\tset refresh time in secends.\n"
				"\t-i<IP address>\t server ip address.\n");
			exit(0);
		case 'd':
			debug++;
			break;
		case 'r':
			refresh_time=atoi(optarg);
			break;
		case 'i':
			server_ip=inet_addr(optarg);
			break;
	}
}

static void handle_msg_updat(const int sockfd, const char nr, const char pri, const char *mesg, const int len) 
{
	int i;
	i = msglcd_updat(sockfd, nr, pri, mesg, len);
	if ( i == 0 ) return;
	fatal("could not update mesg. msglcd_updat returned: %d", i);
}

void CloseAlsa(void) 
{ 
	snd_mixer_close(mixer_handle); 
}

int InitAlsa(void)
{
	int err;
	const char *card = "default";
	 
	err = snd_mixer_open(&mixer_handle, 0);
	if (err < 0 ) goto err_1;
	err = snd_mixer_attach(mixer_handle, card);
	if (err < 0 ) goto err_2;
	err = snd_mixer_selem_register(mixer_handle, NULL, NULL);
	if (err < 0 ) goto err_2;
	err = snd_mixer_load(mixer_handle);
	if (err < 0 ) goto err_2;
	
	return 0; /*succes */
err_2:
	snd_mixer_close(mixer_handle);
err_1:
	return -1;
}

int GetAlsaMasterVolume(long *max, long *outvol, int *value)
{
	int err;
	long min;
	snd_mixer_elem_t* elem;
	snd_mixer_selem_id_t *sid;
	const char *selem_name = "Master";
	
	snd_mixer_selem_id_alloca(&sid);
	snd_mixer_selem_id_set_index(sid, 0);
	snd_mixer_selem_id_set_name(sid, selem_name);

	elem = snd_mixer_find_selem(mixer_handle, sid);
	if ( !elem ) goto err_1;
	err = snd_mixer_selem_get_playback_switch(elem, 0, value);
	if ( err < 0 ) goto err_1;
	err = snd_mixer_selem_get_playback_volume_range(elem, (long *)&min, max);
	if (err < 0 ) goto err_1;
	err = snd_mixer_selem_get_playback_volume(elem, 0, outvol);
	if (err < 0 ) goto err_1;
	
	PDEBUG(" min %ld max %ld outvol %ld ", min, *max, *outvol);
	*max = *max - min;  /* min is now zero */
	return 0; /* succes */
	/* errors */
err_1:
	return -1;
}

int msglcd_test_fd(const int fd, const char mode, const int tim_sec, const int tim_usec)
{
	/* mode = 'r' - test fd for incoming mesg
	 * mode = 'w' - test fd fot outgoing mesg (?)
	 * mode = 'b' - both [:-D */
	fd_set set;
	struct timeval tv;
	tv.tv_sec = tim_sec;
	tv.tv_usec = tim_usec;
	FD_ZERO(&set);
	FD_SET(fd,&set);
	switch (mode) {
	case 'r':
		return(select(fd+1,&set,NULL,NULL,&tv));
	case 'w':
		return(select(fd+1,NULL,&set,NULL,&tv));
	case 'b':
		return(select(fd+1,&set,&set,NULL,&tv));
	}
	return(-2);
}

void update_message(long outvol_new, long max_new, int value_new)
{
	static long max = 0, outvol = 0;
	static int value = 0;
	
	char buff[41] = "Vol[                                   ]";
	
	if (max == max_new && value == value_new && outvol == outvol_new)
		return;
	max = max_new;
	value = value_new;
	outvol = outvol_new;

	//PDEBUG(" max: %d outvol: %ld switchL %ld", max, outvol, value);
	if ( value ) {
		int i;
		outvol = (BAR_WIDTH * outvol) / max;
		for (i = 0; i < (outvol/5); i++) 
			buff[i+4]=255;
		if ((outvol%5) != 0)
			buff[outvol/5+4] = (outvol%5)-1;
	} else {
		memcpy(buff + 4, "- - - - - - - - MUTE - - - - - - - - ", 35*sizeof(char));
	}
	
	PDEBUG("%s", buff);
	handle_msg_updat(sockfd, 0, MSGPRI, buff, 40);
}

int poll_wait(void)
{
	/* return 1 when new alsa event */
	
	int count;
	int ret = 0;
	unsigned short revents;
	PDEBUGL(2 ,"Poll alsa");
	struct pollfd *fds;
		
	/* setup for select on stdin and the mixer fd */
	count = snd_mixer_poll_descriptors_count(mixer_handle);
	if (count < 0)
		fatal("snd_mixer_poll_descriptors_count");
	fds = calloc(count+1, sizeof(struct pollfd));
	memset(fds, 0, (count+1)*sizeof(*fds));
	if (!fds)
		fatal("calloc");
	ret = snd_mixer_poll_descriptors(mixer_handle, fds, count);
	if (ret < 0)
		fatal ("snd_mixer_poll_descriptors");
	if (ret != count) 
		fatal ("snd_mixer_poll_descriptors (err != count)");
	
	
	fds[count].fd = sockfd;
	fds[count].events = POLLIN;
	
	ret = poll(fds, count+1, refresh_time * 1000); /* smart sleep */
	
	if (ret < 0 && errno == EINTR) ret = 0;
	if (ret > 0 && (fds[count].revents & POLLIN) ) {
		int i;
		i = msglcd_handle_server(sockfd);
		if ( i < 0 ) { 
			free(fds);
			fatal("Server aclosed the connection or strange error occured: %d ", i);
		}
		PDEBUGL(2, "msglcd_handle_server got %d ", i);
		ret--;
	}
	
	if (ret > 0) {
		if (snd_mixer_poll_descriptors_revents(mixer_handle, fds, count, &revents) >= 0) {
			if (revents & POLLIN) {
				/** here !! */
				PDEBUGL(2, "new alsa event ");
				snd_mixer_handle_events(mixer_handle);
				return(1);
			}
			if (revents & POLLNVAL) {
				free(fds);
				fatal("snd_mixer_poll_descriptors (POLLNVAL)");
			}
			if (revents & POLLERR) {
				free(fds);
				fatal("snd_mixer_poll_descriptors (POLLERR)");
			}
		}
	}
	free(fds);
	return(0);
}

int main(int argc, char **argv)
{
	signal(SIGHUP,sig_hup);
	signal(SIGQUIT,sig_hup);
	signal(SIGTERM,sig_hup);
	signal(SIGINT,sig_hup);
	
	parse_cmd(argc,argv);
	if (!server_ip) {
		printf(" pls give server ip address \n");
		exit(0);
	}
	
	
	if ( InitAlsa() < 0 )
		fatal("Cannot connect to alsa");
	
	sockfd = msglcd_connect(server_ip);
	if ( sockfd < 0) {
		CloseAlsa();
		fatal("Could not connect to lcd server: %d \n", sockfd);
	}
	handle_msg_updat(sockfd, 0, MSGPRI, " ", 1);

	if ( !debug ) { 
		PDEBUG("Startup succed");
	}
	
	PDEBUG("Connected with %s.\n", inet_ntoa(*(struct in_addr *)&server_ip));
	
	{ /* init message */
		long max = 0, outvol = 0;
		int value = 0;
		if ( GetAlsaMasterVolume(&max, &outvol, &value) < 0 ) {
			fatal("Cannot get master volume ");
		}
		update_message(outvol, max, value);
	}
	
		
	/* stopped only by signals */
	for (;;) {
		if ( poll_wait() == 1 ) {
			long max = 0, outvol = 0;
			int value = 0;
			if ( GetAlsaMasterVolume(&max, &outvol, &value) < 0 ) {
				fatal("Cannot get master volume ");
			}
			update_message(outvol, max, value);
		}
	} /* for (;;) */
}

